package com.xiaoma.mvi

import android.os.Bundle
import android.view.View
import android.view.ViewGroup
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.fragment.app.viewModels
import androidx.lifecycle.*
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.ViewHolder
import androidx.transition.TransitionInflater
import com.xiaoma.mvi.business.Dimens
import com.xiaoma.mvi.databinding.FragmentLoadingListSelectionBinding
import com.xiaoma.mvi.databinding.ViewLoadingListSelectionListItemBinding
import com.xiaoma.mvi.func.BindingFragment
import com.xiaoma.mvi.func.get
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch

class LoadingListSelectionFragment : BindingFragment<FragmentLoadingListSelectionBinding>() {

    private val vm by viewModels<Vm>()
    private val downloadVm by viewModels<DownloadVm>()

    override fun onResume() {
        super.onResume()
        enterTransition = TransitionInflater.from(requireContext())
            .inflateTransition(R.transition.fragment_exit)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        binding.prepareView()
    }

    private fun FragmentLoadingListSelectionBinding.prepareView() {
        ViewCompat.setOnApplyWindowInsetsListener(rvList) { view, windowInsets ->
            val type = WindowInsetsCompat.Type.systemBars()
            val inset = windowInsets.getInsets(type)
            view.setPadding(
                inset.left + Dimens.viewPaddingHorizontal,
                inset.top + Dimens.viewPaddingTop,
                inset.right + Dimens.viewPaddingHorizontal,
                inset.bottom + Dimens.viewPaddingBottom
            )
            WindowInsetsCompat.CONSUMED
        }

        rvList.adapter = Adapter(
            vm.dataListFlow,
            vm.selectStateFlow,
            downloadVm::downloadState,
            viewLifecycleOwner,
        ) { item ->
            vm.selectItem(item)
            downloadVm.stopAllJobs()
            if (vm.selectStateFlow.value.isSelected(item)) {
                downloadVm.startJob(item)
            }
        }
    }

    private class Diff(
        val oldList: List<Item>,
        val newList: List<Item>,
    ) : DiffUtil.Callback() {

        override fun getOldListSize() = oldList.size
        override fun getNewListSize() = newList.size

        override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int) =
            oldList[oldItemPosition].id == newList[newItemPosition].id

        override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int) =
            listOf(
                oldList[oldItemPosition].name == newList[newItemPosition].name,
            ).all { true }
    }

    private class Adapter(
        dataListFlow: Flow<List<Item>>,
        private val selectStateFlow: Flow<SelectState>,
        private val downloadStateFlow: (Item) -> Flow<DownloadState>,
        private val lifecycleOwner: LifecycleOwner,
        private val onItemClick: (item: Item) -> Unit,
    ) : RecyclerView.Adapter<Holder>() {

        private var dataList: List<Item> = emptyList()
        private val dataListFlow = dataListFlow.stateIn(
            lifecycleOwner.lifecycleScope,
            SharingStarted.Lazily,
            emptyList()
        )

        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
            Holder(parent, lifecycleOwner, selectStateFlow, downloadStateFlow, onItemClick)

        override fun onBindViewHolder(holder: Holder, position: Int) {
            holder.refresh(dataListFlow.value[position])
        }

        override fun getItemCount() = dataListFlow.value.size

        init {
            lifecycleOwner.lifecycleScope.launch {
                lifecycleOwner.repeatOnLifecycle(Lifecycle.State.RESUMED) {
                    this@Adapter.dataListFlow.collect { newList ->
                        val oldList = dataList
                        dataList = newList
                        DiffUtil.calculateDiff(Diff(oldList, newList))
                            .dispatchUpdatesTo(this@Adapter)
                    }
                }
            }
        }
    }

    private class Holder(
        parent: ViewGroup,
        lifecycleOwner: LifecycleOwner,
        selectStateFlow: Flow<SelectState>,
        downloadStateFlow: (Item) -> Flow<DownloadState>,
        private val onItemClick: (item: Item) -> Unit,
    ) : ViewHolder(parent[R.layout.view_loading_list_selection_list_item]) {

        private val binding = ViewLoadingListSelectionListItemBinding.bind(itemView)

        private val bindingItem = MutableStateFlow<Item?>(null)
        fun refresh(item: Item) = bindingItem.update { item }

        private val itemSelectStateFlow: Flow<Boolean> = combine(
            bindingItem,
            selectStateFlow,
        ) { item, selectState ->
            selectState.isSelected(item)
        }

        @OptIn(ExperimentalCoroutinesApi::class)
        private val itemDownloadStateFlow: Flow<DownloadState> =
            bindingItem.flatMapLatest { item: Item? ->
                when (item) {
                    null -> emptyFlow()
                    else -> downloadStateFlow(item)
                }
            }

        init {
            lifecycleOwner.collectState()
        }

        private fun LifecycleOwner.collectState() = lifecycleScope.apply {

            launch {
                repeatOnLifecycle(Lifecycle.State.RESUMED) {
                    bindingItem.map { it?.name }.collect { name ->
                        binding.tvText.text = name ?: ""
                    }
                }
            }

            launch {
                repeatOnLifecycle(Lifecycle.State.RESUMED) {
                    bindingItem.collect { item ->
                        binding.root.setOnClickListener {
                            item?.let(onItemClick)
                        }
                    }
                }
            }

            launch {
                repeatOnLifecycle(Lifecycle.State.RESUMED) {
                    itemSelectStateFlow.collect { selected: Boolean ->
                        binding.vSelected.visibility = when (selected) {
                            true -> View.VISIBLE
                            false -> View.GONE
                        }
                        binding.sivBg.isEnabled = selected
                    }
                }
            }

            launch {
                repeatOnLifecycle(Lifecycle.State.RESUMED) {
                    itemDownloadStateFlow.collect { downloadState ->
                        val loadingVisibility = when (downloadState) {
                            DownloadState.Downloading -> View.VISIBLE
                            else -> View.GONE
                        }
                        binding.pbLoading.visibility = loadingVisibility
                        binding.vLoadingMask.visibility = loadingVisibility
                    }
                }
            }

        }
    }

    sealed interface SelectState {
        object None : SelectState
        data class Selected(val item: Item) : SelectState

        fun isSelected(item: Item?) = when (this) {
            is Selected -> (this.item == item)
            else -> false
        }
    }

    class Vm : ViewModel() {
        private val repo = Repository()
        val dataListFlow = repo.dataListFlow

        private val _selectStateFlow = MutableStateFlow<SelectState>(SelectState.None)
        val selectStateFlow = _selectStateFlow.asStateFlow()
        fun selectItem(item: Item) = _selectStateFlow.update {
            when (it.isSelected(item)) {
                true -> SelectState.None
                false -> SelectState.Selected(item)
            }
        }

    }

    private class Repository {

        private val dataList = sequence {
            for (id in 0..99) {
                yield(Item(id, id.toString()))
            }
        }.toList()

        private val _dataListFlow = MutableStateFlow(dataList)
        val dataListFlow = _dataListFlow.asStateFlow()

    }

    enum class DownloadState {
        Unknown,
        Downloading,
        Finish
    }

    sealed interface DownloadJobState {
        object None : DownloadJobState
        data class Job(val item: Item) : DownloadJobState
    }

    class DownloadVm : ViewModel() {

        private val downloadStateFlow =
            MutableStateFlow<Map<Item, DownloadState>>(emptyMap())
        fun downloadState(item: Item): Flow<DownloadState> =
            downloadStateFlow.map { it[item] ?: DownloadState.Unknown }

        private val downloadJobState =
            MutableStateFlow<DownloadJobState>(DownloadJobState.None)
        fun startJob(item: Item) = downloadJobState.update { DownloadJobState.Job(item) }
        fun stopAllJobs() = downloadJobState.update { DownloadJobState.None }

        init {
            viewModelScope.launch {
                downloadJobState.collectLatest { state ->
                    downloadStateFlow.update { map ->
                        map.filterValues { it != DownloadState.Downloading }
                    }

                    when (state) {
                        DownloadJobState.None -> Unit
                        is DownloadJobState.Job -> {
                            val item = state.item
                            val isFinish = downloadStateFlow.value[item] == DownloadState.Finish
                            if (isFinish) return@collectLatest

                            downloadStateFlow.update { it + (item to DownloadState.Downloading) }
                            download(item)
                            downloadStateFlow.update { it + (item to DownloadState.Finish) }
                        }
                    }
                }
            }
        }
    }

    data class Item(val id: Int, val name: String)

}

@Suppress("UNUSED_PARAMETER")
suspend fun download(item: LoadingListSelectionFragment.Item) {
    // 模拟下载
    delay(1_000L)
}